import { generatePath } from 'react-router-dom-v5-compat';

import type { ApiSortOption, SearchFilter } from 'types/search';
import type { SlimUser } from 'types/user.proto';
import { getListQueryParams } from 'utils/searchUtils';
import type { Empty } from './types';
import axios from './instance';

const baseUrl = '/v2/vulnerability-exceptions';

export type VulnerabilityState = 'OBSERVED' | 'DEFERRED' | 'FALSE_POSITIVE';

export type ExceptionStatus = 'PENDING' | 'APPROVED' | 'DENIED' | 'APPROVED_PENDING_UPDATE';

export type VulnerabilityExceptionComment = {
    id: string;
    message: string;
    user: SlimUser;
    createdAt: string; // ISO 8601
};

export type VulnerabilityExceptionScope = {
    imageScope: {
        registry: string;
        remote: string;
        tag: string;
    };
};

export type ExceptionExpiry =
    | {
          expiryType: 'TIME';
          expiresOn: string | null;
      }
    | {
          expiryType: 'ALL_CVE_FIXABLE' | 'ANY_CVE_FIXABLE';
      };

export type DeferralUpdate = {
    cves: string[];
    expiry: ExceptionExpiry;
};

export type FalsePositiveUpdate = {
    cves: string[];
};

export type BaseVulnerabilityException = {
    id: string;
    name: string;
    status: ExceptionStatus;
    expired: boolean;
    requester?: SlimUser;
    approvers: SlimUser[];
    createdAt: string; // ISO 8601
    lastUpdated: string; // ISO 8601
    comments: VulnerabilityExceptionComment[];
    scope: VulnerabilityExceptionScope;
    cves: string[];
};

export type VulnerabilityDeferralException = BaseVulnerabilityException & {
    targetState: 'DEFERRED';
    deferralRequest: {
        expiry: ExceptionExpiry;
    };
    deferralUpdate?: DeferralUpdate;
};

export function isDeferralException(
    exception: BaseVulnerabilityException
): exception is VulnerabilityDeferralException {
    return 'targetState' in exception && exception.targetState === 'DEFERRED';
}

export type VulnerabilityFalsePositiveException = BaseVulnerabilityException & {
    targetState: 'FALSE_POSITIVE';
    falsePositiveRequest: Empty;
    falsePositiveUpdate?: FalsePositiveUpdate;
};

export function isFalsePositiveException(
    exception: BaseVulnerabilityException
): exception is VulnerabilityFalsePositiveException {
    return 'targetState' in exception && exception.targetState === 'FALSE_POSITIVE';
}

export type VulnerabilityException =
    | VulnerabilityDeferralException
    | VulnerabilityFalsePositiveException;

export function fetchVulnerabilityExceptionById(id: string): Promise<VulnerabilityException> {
    const url = generatePath(`${baseUrl}/:id`, { id });
    return axios
        .get<{ exception: VulnerabilityException }>(url)
        .then((response) => response.data.exception);
}

export function fetchVulnerabilityExceptions(
    searchFilter: SearchFilter,
    sortOption: ApiSortOption,
    page: number,
    perPage: number
): Promise<VulnerabilityException[]> {
    const params = getListQueryParams({ searchFilter, sortOption, page, perPage });

    return axios
        .get<{
            exceptions: VulnerabilityException[];
        }>(`${baseUrl}?${params}`)
        .then((response) => response.data.exceptions);
}

export type CreateDeferVulnerabilityExceptionRequest = {
    cves: string[];
    comment: string;
    scope: VulnerabilityExceptionScope;
    exceptionExpiry: ExceptionExpiry;
};

export function createDeferralVulnerabilityException(
    payload: CreateDeferVulnerabilityExceptionRequest
): Promise<VulnerabilityDeferralException> {
    const url = `${baseUrl}/deferral`;
    return axios
        .post<{ exception: VulnerabilityDeferralException }>(url, payload)
        .then((response) => response.data.exception);
}

export type CreateFalsePositiveVulnerabilityExceptionRequest = {
    cves: string[];
    comment: string;
    scope: VulnerabilityExceptionScope;
};

export function createFalsePositiveVulnerabilityException(
    payload: CreateFalsePositiveVulnerabilityExceptionRequest
): Promise<VulnerabilityFalsePositiveException> {
    const url = `${baseUrl}/false-positive`;
    return axios
        .post<{ exception: VulnerabilityFalsePositiveException }>(url, payload)
        .then((response) => response.data.exception);
}

export type UpdateVulnerabilityExceptionRequest =
    | {
          id: string;
          comment: string;
          deferralUpdate: DeferralUpdate;
      }
    | {
          id: string;
          comment: string;
          falsePositiveUpdate: FalsePositiveUpdate;
      };

export function updateVulnerabilityException(
    payload: UpdateVulnerabilityExceptionRequest
): Promise<VulnerabilityException> {
    const url = `${baseUrl}/${payload.id}`;
    return axios
        .patch<{ exception: VulnerabilityException }>(url, payload)
        .then((response) => response.data.exception);
}

export type ApproveVulnerabilityExceptionRequest = {
    id: string;
    comment: string;
};

export function approveVulnerabilityException(
    payload: ApproveVulnerabilityExceptionRequest
): Promise<VulnerabilityException> {
    const { id, comment } = payload;
    const url = `${baseUrl}/${id}/approve`;
    return axios
        .post<{ exception: VulnerabilityException }>(url, {
            comment,
        })
        .then((response) => response.data.exception);
}

export type DenyVulnerabilityExceptionRequest = {
    id: string;
    comment: string;
};

export function denyVulnerabilityException(
    payload: DenyVulnerabilityExceptionRequest
): Promise<VulnerabilityException> {
    const { id, comment } = payload;
    const url = `${baseUrl}/${id}/deny`;
    return axios
        .post<{ exception: VulnerabilityException }>(url, {
            comment,
        })
        .then((response) => response.data.exception);
}

export type CancelVulnerabilityExceptionRequest = {
    id: string;
};

export function cancelVulnerabilityException(
    payload: CancelVulnerabilityExceptionRequest
): Promise<VulnerabilityException> {
    const { id } = payload;
    const url = `${baseUrl}/${id}/cancel`;
    return axios
        .post<{ exception: VulnerabilityException }>(url)
        .then((response) => response.data.exception);
}
